import MarkdownIt from 'markdown-it';

export type RenderCallBack = (root: HTMLElement, tmpDiv: HTMLElement, ignoreInstance: string[]) => void

export class MarkdownRender {
    private root: HTMLElement;
    private tmpDiv: HTMLElement;
    private ignoreInstance: string[];
    private renderQu: string[] = [];
    private md: MarkdownIt;

    constructor(md: MarkdownIt, root: HTMLElement, tmpDiv: HTMLElement, ignoreInstance: string[] = []) {
        this.md = md;
        this.root = root;
        this.tmpDiv = tmpDiv;
        this.ignoreInstance = ignoreInstance
    }

    /**
     * 里面使用异步渲染
     * @param content
     * @param call
     */
    public rendMd(content: string, call?: RenderCallBack) {
        this.renderQu.push(content)
        // 使用宏任务减轻浏览器渲染负担
        setTimeout(() => {
            if (this.renderQu.length) {
                // 直接获取最后一个
                let nowContent = this.renderQu[this.renderQu.length - 1]
                this.tmpDiv.innerHTML = this.md.render(nowContent)
                this.deepCloneAndUpdate()
                // 直接清空队列
                this.renderQu = []
                if (call) {
                    call(this.root, this.tmpDiv, this.ignoreInstance)
                }
            }
        }, 50)
    }

    private deepCloneAndUpdate() {
        // 递归比较和更新 root 和 tmpDiv 的子节点
        let compareAndUpdate = (node1, node2) => {
            // 情况 0：node1 已经被其他插件控制，例如echarts，不再处理
            let find = this.ignoreInstance.find(i => node1[i])
            if (find) {
                return
            }
            // 情况 1：node1 是文本节点，更新文本内容
            if (
                node1 &&
                node1.nodeType === Node.TEXT_NODE &&
                node2.nodeType === Node.TEXT_NODE
            ) {
                if (node1.nodeValue !== node2.nodeValue) {
                    // 更新文本内容
                    node1.nodeValue = node2.nodeValue
                }
                return
            }

            // 情况 2：node1 和 node2 的标签名不同，替换整个节点
            if (!node1 || node1.tagName !== node2.tagName) {
                // 克隆 node2 节点
                const newNode = node2.cloneNode(true)
                if (node1) {
                    // 替换旧节点
                    node1.parentNode.replaceChild(newNode, node1)
                } else {
                    // 如果 node1 不存在，直接新增
                    node2.parentNode.appendChild(newNode)
                }
                return
            }

            // 情况 3：节点的 class 或其他属性更新, 注意对root节点的保护
            if (
                node1.className !== 'article-content' &&
                node1.className !== node2.className
            ) {
                // 3.1 更新 className
                node1.className = node2.className
            }

            // 3.2 对 id 的更新 注意对root节点的保护
            if (node1.id !== 'article-content' && node1.id !== node2.id) {
                node1.id = node2.id
            }

            //  3.3 对 style 的更新
            if (node1.style.cssText !== node2.style.cssText) {
                node1.style.cssText = node2.style.cssText
            }

            // 情况 4：递归对比和更新子节点
            const children1 = Array.from(node1.childNodes) as HTMLElement[] // node1 的所有子节点
            const children2 = Array.from(node2.childNodes) as HTMLElement[] // node2 的所有子节点

            // 遍历 node2 的子节点，逐个与 node1 的对应子节点比较
            children2.forEach((child2, index) => {
                const child1 = children1[index]
                if (!child1) {
                    // 如果 child1 不存在，直接克隆 child2 并添加到 node1
                    const newChild = child2.cloneNode(true)
                    node1.appendChild(newChild)
                } else {
                    // 如果 child1 存在，递归比较和更新
                    compareAndUpdate(child1, child2)
                }
            })

            // 删除 node1 中多余的子节点
            if (children1.length > children2.length) {
                for (let i = children2.length; i < children1.length; i++) {
                    node1.removeChild(children1[i])
                }
            }
        }

        // 从 tmpDiv 根节点开始与 root 比较
        compareAndUpdate(this.root, this.tmpDiv)
    }
}
